import Dep from "./observe/dep";
import { observe } from "./observe/index";
import Watcher from "./observe/watcher";

/* 初始化状态，处理传递过来的选项 */
export function initState(vm) {
  const opts = vm.$options; //获取所有的选项

  // 处理data选项
  if (opts.data) {
    initData(vm);
  }

  // 处理computed选项
  if (opts.computed) {
    initComputed(vm);
  }
}

/* 初始化data */
function initData(vm) {
  let data = vm.$options.data; //data可能是函数和对象（这里data是用户返回的对象）

  // 保证data是对象
  data = typeof data == "function" ? data.call(vm) : data;

  vm._data = data;

  //  对数据进行劫持 Vue2里面采用了Object.defineProperty
  /*
      注意:这里只劫持let定义的变量data，vm.$options.data仍然看不到劫持
      > 在实例身上添加一个_data,将劫持好的data赋值给他，
        这样在_data中就可以看到劫持到的属性
  */
  observe(data);

  // 将vm._data用vm来代理，不然每次都得vm._data.xxx来获取值
  for (let key in data) {
    proxy(vm, "_data", key);
  }
}

/* vm代理vm._data  实例上存取值，在_data上面存取*/
function proxy(vm, target, key) {
  Object.defineProperty(vm, key, {
    // vm.name
    get() {
      return vm[target][key]; // vm._data.name
    },
    set(newValue) {
      vm[target][key] = newValue;
    },
  });
}

/* 初始化计算属性 */
function initComputed(vm) {
  const computed = vm.$options.computed;
  // 将计算属性watcher保存到vm上
  const watchers = (vm._computedWatchers = {});
  for (let key in computed) {
    let userDef = computed[key];

    let getter = typeof userDef === "function" ? userDef : userDef.get;
    // 初始化计算属性也创建一个watcher，但计算属性发生变化，重新执行get函数
    // 如果直接new Watcher默认会执行传入的函数，给个标识lazy控制其不要立即执行
    // 并赋值一个对象结构，让属性和watcher对应起来
    watchers[key] = new Watcher(vm, getter, { lazy: true });
    // 将计算属性中的东西定义为属性
    defineComputed(vm, key, userDef);
  }
}

function defineComputed(target, key, userDef) {
  // const getter = typeof userDef === "function" ? userDef : userDef.get;
  const setter = userDef.set || (() => {});

  // 利用Object.defineProperty 可以通过实例拿到对应的属性
  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter,
  });
}

// 重新包装一下getter函数，多次取值要控制dirty属性，根据dirty属性控制执行还是不执行
// 计算属性不会收集依赖，只会让自己依赖的属性去收集依赖
function createComputedGetter(key) {
  return function () {
    const watcher = this._computedWatchers[key]; // 获取到对应属性的watcher
    // 多次取计算属性上的值，只有第一次的时候值是脏的
    if (watcher.dirty) {
      // 如果是脏的就去执行用户传入的函数，也即getter函数
      watcher.evaluate();
      // 求值后dirty变为了false，下次就不求值了，直接走缓存
      console.log("计算属性获取值");
    }
    if (Dep.target) {
      // 计算属性出栈后 还有渲染watcher，则应该让计算属性watcher里面的属性去收集上一层watcher
      // 这样当计算属性依赖的值变化后，不仅需要刷新计算属性，还需要刷新视图
      watcher.depend();
    }

    return watcher.value; // 最后返回的是watcher上的值
  };
}
